package com.adv.ems.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.json.JSONUtil;
import com.adv.ems.constants.EmsConst;
import com.adv.ems.constants.SystemConst;
import com.adv.ems.db.entity.*;
import com.adv.ems.enums.CollectPointDataTypeEnum;
import com.adv.ems.enums.DateTypeEnum;
import com.adv.ems.enums.SubjectTypeEnum;
import com.adv.ems.manager.*;
import com.adv.ems.model.dto.*;
import com.adv.ems.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class BaseServiceImpl {
    @Resource
    CircuitDiagramNodeMeterRelationManager circuitDiagramNodeMeterRelationManager;
    @Resource
    CircuitDiagramNodeEnergySubjectRelationManager circuitDiagramNodeEnergySubjectRelationManager;
    @Resource
    CircuitDiagramNodeManager circuitDiagramNodeManager;
    @Resource
    BuildingInfoManager buildingInfoManager;
    @Resource
    UserManager userManager;
    @Resource CollectPointManager collectPointManager;
    @Resource EnergyDataCumulateManager energyDataCumulateManager;
    @Resource EnergyDataCumulateReportHourManager energyDataCumulateReportHourManager;
    @Resource EnergyDataCumulateReportDayManager energyDataCumulateReportDayManager;
    @Resource EnergyDataCumulateReportMonthManager energyDataCumulateReportMonthManager;
    @Resource EnergyDataCumulateReportYearManager energyDataCumulateReportYearManager;
    @Resource MeterManager meterManager;
    @Resource ItemInfoManager itemInfoManager;
    @Resource ItemOneInfoManager itemOneInfoManager;
    @Resource ItemTwoInfoManager itemTwoInfoManager;
    @Resource EnergyInfoManager energyInfoManager;
    @Resource BuildingTypeInfoManager buildingTypeInfoManager;
    @Resource DepartmentInfoManager departmentInfoManager;
    @Resource InstituteInfoManager instituteInfoManager;
    @Resource EnergyDataProcessManager energyDataProcessManager;
    @Resource CollectPointFunctionManager collectPointFunctionManager;
    @Resource EnergyReportTemplateManager energyReportTemplateManager;

    public UserSubjectInfo getUserSubjectInfo(UserDO user) {
        UserSubjectInfo userSubjectInfo;
        String relateSubject = user.getRelateSubject();
        if (relateSubject == null) {
            userSubjectInfo = new UserSubjectInfo();
        }else {
            userSubjectInfo = JSONUtil.toBean(relateSubject, UserSubjectInfo.class);
        }
        return userSubjectInfo;
    }

    public UserDO getCurrentUser() {
        LoginInfo loginUser = JwtUtil.getLoginUser();
        UserDO user = userManager.getUserByUsername(loginUser.getUsername());
        if (user == null) {
            throw new BusinessException(SystemConst.ErrorCode.NO_LOGIN, "账号不存在");
        }
        return user;
    }

    public EnergyDataResultDTO getAllPointEnergy(DateTypeEnum sourceDataType,
                                      Date startDate,
                                      Date endDate,
                                      List<Long> AllPointIds) {
        List<Long> pointIds = new ArrayList<>();
        List<Long> processPointIds = new ArrayList<>();
        for (Long id : AllPointIds) {
            CollectPointDO p = collectPointManager.getAllMap().get(id);
            if (p == null) {
                continue;
            }
            if (p.getDataType() != null && p.getDataType().equals(CollectPointDataTypeEnum.PROCESS.name())) {
                processPointIds.add(p.getId());
            }
            if (p.getDataType() != null && p.getDataType().equals(CollectPointDataTypeEnum.CUMULATE.name())) {
                pointIds.add(p.getId());
            }
        }
        if (CollectionUtil.isNotEmpty(processPointIds)) {
            List<QueryPointEnergyResultDTO> list = new ArrayList<>();
            List<EnergyDataProcessDO> data = energyDataProcessManager.selectData(startDate, endDate, processPointIds);
            data.forEach(d -> {
                QueryPointEnergyResultDTO dto = new QueryPointEnergyResultDTO();
                dto.setDate(d.getColDate());
                dto.setPointId(d.getCollectPointId());
                dto.setIncreaseCashValue(0D);
                dto.setIncreaseStandValue(0D);
                dto.setIncreaseRealValue(d.getRealValue());
                list.add(dto);
            });
            EnergyDataResultDTO resultDTO = new EnergyDataResultDTO();
            resultDTO.setList(list);
            resultDTO.setProcess(true);
            return resultDTO;
        }
        List<QueryPointEnergyResultDTO> pointEnergy = getPointEnergy(sourceDataType, startDate, endDate, pointIds);
        EnergyDataResultDTO resultDTO = new EnergyDataResultDTO();
        resultDTO.setList(pointEnergy);
        resultDTO.setProcess(false);
        return resultDTO;
    }
    public List<QueryPointEnergyResultDTO> getPointEnergy(DateTypeEnum sourceDataType,
                                                          Date startDate,
                                                          Date endDate,
                                                          List<Long> pointIds) {
        List<QueryPointEnergyResultDTO> list = new ArrayList<>();
        if (CollectionUtil.isEmpty(pointIds)) {
            return list;
        }
        if (DateTypeEnum.min.equals(sourceDataType)) {
            List<EnergyDataCumulateDO> data = energyDataCumulateManager.selectData(startDate, endDate, pointIds);
            data.forEach(d -> {
                QueryPointEnergyResultDTO dto = new QueryPointEnergyResultDTO();
                dto.setDate(d.getColDate());
                dto.setPointId(d.getCollectPointId());
                dto.setIncreaseCashValue(0D);
                dto.setIncreaseStandValue(d.getIncreaseStandValue());
                dto.setIncreaseRealValue(d.getIncreaseRealValue());
                list.add(dto);
            });
        }else if (DateTypeEnum.hour.equals(sourceDataType)) {
            List<EnergyDataCumulateReportHourDO> data = energyDataCumulateReportHourManager.selectData(startDate, endDate, pointIds);
            for (EnergyDataCumulateReportHourDO d : data) {
                QueryPointEnergyResultDTO dto = new QueryPointEnergyResultDTO();
                dto.setPointId(d.getCollectPointId());
                dto.setDate(d.getColDate());
                dto.setIncreaseCashValue(d.getIncreaseCashValue());
                dto.setIncreaseStandValue(d.getIncreaseStandValue());
                dto.setIncreaseRealValue(d.getIncreaseRealValue());
                dto.setFirstRealValue(d.getFirstRealValue());
                dto.setLastRealValue(d.getLastRealValue());
                list.add(dto);
            }
        }else if (DateTypeEnum.day.equals(sourceDataType)) {
            List<EnergyDataCumulateReportDayDO> data = energyDataCumulateReportDayManager.selectData(startDate, endDate, pointIds);
            for (EnergyDataCumulateReportDayDO d : data) {
                QueryPointEnergyResultDTO dto = new QueryPointEnergyResultDTO();
                dto.setPointId(d.getCollectPointId());
                dto.setDate(d.getColDate());
                dto.setIncreaseCashValue(d.getIncreaseCashValue());
                dto.setIncreaseStandValue(d.getIncreaseStandValue());
                dto.setIncreaseRealValue(d.getIncreaseRealValue());
                dto.setFirstRealValue(d.getFirstRealValue());
                dto.setLastRealValue(d.getLastRealValue());
                list.add(dto);
            }
        }else if (DateTypeEnum.month.equals(sourceDataType)) {
            List<EnergyDataCumulateReportMonthDO> data = energyDataCumulateReportMonthManager.selectData(startDate, endDate, pointIds);
            for (EnergyDataCumulateReportMonthDO d : data) {
                QueryPointEnergyResultDTO dto = new QueryPointEnergyResultDTO();
                dto.setPointId(d.getCollectPointId());
                dto.setDate(d.getColDate());
                dto.setIncreaseCashValue(d.getIncreaseCashValue());
                dto.setIncreaseStandValue(d.getIncreaseStandValue());
                dto.setIncreaseRealValue(d.getIncreaseRealValue());
                dto.setFirstRealValue(d.getFirstRealValue());
                dto.setLastRealValue(d.getLastRealValue());
                list.add(dto);
            }
        }else if (DateTypeEnum.year.equals(sourceDataType)) {
            List<EnergyDataCumulateReportYearDO> data = energyDataCumulateReportYearManager.selectData(startDate, endDate, pointIds);
            for (EnergyDataCumulateReportYearDO d : data) {
                QueryPointEnergyResultDTO dto = new QueryPointEnergyResultDTO();
                dto.setPointId(d.getCollectPointId());
                dto.setDate(d.getColDate());
                dto.setIncreaseCashValue(d.getIncreaseCashValue());
                dto.setIncreaseStandValue(d.getIncreaseStandValue());
                dto.setIncreaseRealValue(d.getIncreaseRealValue());
                dto.setFirstRealValue(d.getFirstRealValue());
                dto.setLastRealValue(d.getLastRealValue());
                list.add(dto);
            }
        }
        return list;
    }

    public List<Long> getAllPointIds(List<Long> meterIds, Long energyId, Long itemId, Long oneItem, Long twoItem) {
        Map<Long, MeterDO> meterDOMap = meterManager.getAllMap();
        List<Long> filterMeterIds = meterIds
                .stream()
                .map(meterDOMap::get)
                .filter(Objects::nonNull)
                .filter(m -> energyId == null || energyId.equals(m.getEnergyId()))
                .filter(m -> itemId == null || itemId.equals(m.getItemId()))
                .filter(m -> oneItem == null || oneItem.equals(m.getItemOneId()))
                .filter(m -> twoItem == null || twoItem.equals(m.getItemTwoId()))
                .map(MeterDO::getId)
                .collect(Collectors.toList());
        return collectPointManager
                .getAllMap()
                .values()
                .stream()
                .filter(p -> filterMeterIds.contains(p.getMeterId()))
                .map(CollectPointDO::getId).collect(Collectors.toList());
    }

    public List<Long> getCumulatePointIds(List<Long> meterIds, Long energyId, Long itemId, Long oneItem, Long twoItem) {
        List<Long> allPointIds = getAllPointIds(meterIds, energyId, itemId, oneItem, twoItem);
        return allPointIds
                .stream()
                .map(p -> collectPointManager.getAllMap().get(p))
                .filter(Objects::nonNull)
                .filter(p -> CollectPointDataTypeEnum.CUMULATE.name().equals(p.getDataType()))
                .map(CollectPointDO::getId)
                .collect(Collectors.toList());
    }

    public List<Long> getEnergyMeterIdsByDepartmentId(Long departmentId) {
        List<Long> diagramNodeIds = circuitDiagramNodeEnergySubjectRelationManager.getDiagramNodeIds(ListUtil.of(departmentId), SubjectTypeEnum.DEPARTMENT);
        Map<Long, List<CircuitDiagramNodeDO>> parentIdToChildren = circuitDiagramNodeManager
                .getAllMap()
                .values()
                .stream()
                .filter(node -> node.getParentId() != null)
                .collect(Collectors.groupingBy(CircuitDiagramNodeDO :: getParentId));
        Map<Long, List<CircuitDiagramNodeMeterRelationDO>> nodeToMeterId = circuitDiagramNodeMeterRelationManager.getAllMap().values().stream().collect(Collectors.groupingBy(CircuitDiagramNodeMeterRelationDO::getDiagramNodeId));
        Set<Long> meterIds = new HashSet<>();
        for (Long diagramNodeId : diagramNodeIds) {
            getEnergyMeterIdsByCircuitDiagramNodeId(diagramNodeId, new HashSet<>(), parentIdToChildren, nodeToMeterId, meterIds);
        }
        return new ArrayList<>(meterIds);
    }

    public List<Long> getEnergyMeterIdsByInstitutionId(Long institutionId) {
        List<Long> buildingIds = getBuildingIdsByInstitution(institutionId);
        Set<Long> meterIds = new HashSet<>();
        buildingIds.forEach(buildingId -> meterIds.addAll(getEnergyMeterIdsByBuildingId(buildingId)));
        return new ArrayList<>(meterIds);
    }


    public List<Long> getPointBySubjectInfo(SubjectTypeEnum subjectType, Long subjectId, Long energyId, Long itemId, Long oneItem, Long twoItem) {
        List<Long> meterIds = new ArrayList<>();
        if (SubjectTypeEnum.INSTITUTE.equals(subjectType)) {
            meterIds = getEnergyMeterIdsByInstitutionId(subjectId);
        }else if (SubjectTypeEnum.BUILDING.equals(subjectType)) {
            meterIds = getEnergyMeterIdsByBuildingId(subjectId);
        }else if (SubjectTypeEnum.DEPARTMENT.equals(subjectType)) {
            meterIds = getEnergyMeterIdsByDepartmentId(subjectId);
        }else if (SubjectTypeEnum.METER.equals(subjectType)) {
            meterIds.add(subjectId);
        }else if (SubjectTypeEnum.POINT.equals(subjectType)) {
            return ListUtil.of(subjectId);
        }else if (SubjectTypeEnum.DIAGRAM.equals(subjectType)) {
            List<Long> collect = circuitDiagramNodeMeterRelationManager
                    .getAllMap()
                    .values()
                    .stream()
                    .filter(c -> subjectId.equals(c.getDiagramNodeId()))
                    .map(CircuitDiagramNodeMeterRelationDO::getMeterId)
                    .distinct()
                    .collect(Collectors.toList());
            meterIds.addAll(collect);
        }
        return  getCumulatePointIds(meterIds, energyId, itemId, oneItem, twoItem);
    }
    public List<Long> getPointIdsByUserSubjectInfo(UserSubjectInfo userSubjectInfo, Long energyId, Long itemId, Long oneItem, Long twoItem) {
        if (CollectionUtil.isNotEmpty(userSubjectInfo.getInstitute())) {
            userSubjectInfo.setInstitute(virtualInstituteDeal(userSubjectInfo.getInstitute()));
            return userSubjectInfo
                    .getInstitute()
                    .stream()
                    .flatMap(id -> getPointBySubjectInfo(SubjectTypeEnum.INSTITUTE, id, energyId, itemId, oneItem, twoItem).stream())
                    .distinct()
                    .collect(Collectors.toList());
        }
        if (CollectionUtil.isNotEmpty(userSubjectInfo.getBuilding())){
            return userSubjectInfo
                    .getBuilding()
                    .stream()
                    .flatMap(id -> getPointBySubjectInfo(SubjectTypeEnum.BUILDING, id, energyId, itemId, oneItem, twoItem).stream())
                    .distinct()
                    .collect(Collectors.toList());
        }
        if (CollectionUtil.isNotEmpty(userSubjectInfo.getDepartment())){
            return userSubjectInfo
                    .getDepartment()
                    .stream()
                    .flatMap(id -> getPointBySubjectInfo(SubjectTypeEnum.DEPARTMENT, id, energyId, itemId, oneItem, twoItem).stream())
                    .distinct()
                    .collect(Collectors.toList());
        }
        if (CollectionUtil.isNotEmpty(userSubjectInfo.getMeterIds())) {
            return userSubjectInfo
                    .getMeterIds()
                    .stream()
                    .flatMap(id -> getPointBySubjectInfo(SubjectTypeEnum.METER, id, energyId, itemId, oneItem, twoItem).stream())
                    .distinct()
                    .collect(Collectors.toList());
        }else if (CollectionUtil.isNotEmpty(userSubjectInfo.getPointIds())) {
            return userSubjectInfo.getPointIds();
        }else if (CollectionUtil.isNotEmpty(userSubjectInfo.getDiagramIds())) {
            return userSubjectInfo
                    .getDiagramIds()
                    .stream()
                    .flatMap(id -> getPointBySubjectInfo(SubjectTypeEnum.DIAGRAM, id, energyId, itemId, oneItem, twoItem).stream())
                    .distinct()
                    .collect(Collectors.toList());
        }
        return  new ArrayList<>();
    }

    /**
     * 无建筑院所直接拉取下属分院信息
     * @return
     */
    protected List<Long> virtualInstituteDeal(List<Long> instituteIds) {
        Set<Long> rebuildInstituteIds = new HashSet<>();
        for (Long instituteId : instituteIds) {
            boolean hasBuilding = buildingInfoManager.getAllMap().values().stream().anyMatch(b -> instituteId.equals(b.getInstituteId()));
            if (hasBuilding) {
                rebuildInstituteIds.add(instituteId);
                continue;
            }
            List<Long> childrenIds = instituteInfoManager
                    .getAllMap()
                    .values()
                    .stream()
                    .filter(i -> i.getParentId() != null)
                    .filter(i -> i.getParentId().equals(instituteId))
                    .map(InstituteInfoDO::getId).collect(Collectors.toList());
            rebuildInstituteIds.addAll(childrenIds);
        }
        return new ArrayList<>(rebuildInstituteIds);
    }

    public List<Long> getEnergyMeterIdsByBuildingId(Long buildingId) {
        List<Long> diagramNodeIds = circuitDiagramNodeEnergySubjectRelationManager.getDiagramNodeIds(ListUtil.of(buildingId), SubjectTypeEnum.BUILDING);
        Map<Long, List<CircuitDiagramNodeDO>> parentIdToChildren = circuitDiagramNodeManager
                .getAllMap()
                .values()
                .stream()
                .filter(node -> node.getParentId() != null)
                .collect(Collectors.groupingBy(CircuitDiagramNodeDO :: getParentId));
        Map<Long, List<CircuitDiagramNodeMeterRelationDO>> nodeToMeterId = circuitDiagramNodeMeterRelationManager.getAllMap().values().stream().collect(Collectors.groupingBy(CircuitDiagramNodeMeterRelationDO::getDiagramNodeId));
        Set<Long> meterIds = new HashSet<>();
        for (Long diagramNodeId : diagramNodeIds) {
            getEnergyMeterIdsByCircuitDiagramNodeId(diagramNodeId, new HashSet<>(), parentIdToChildren, nodeToMeterId, meterIds);
        }
        return new ArrayList<>(meterIds);
    }
    private void getEnergyMeterIdsByCircuitDiagramNodeId(Long nodeId,
                                                         Set<Long> existedIds,
                                                         Map<Long, List<CircuitDiagramNodeDO>> parentIdToChildren,
                                                         Map<Long, List<CircuitDiagramNodeMeterRelationDO>> nodeToMeterId,
                                                         Set<Long> meterIds) {
        if (existedIds.contains(nodeId)) {
            log.warn("配电图递归出错，nodeId[{}]链路上存在互为父级关系", nodeId);
            return;
        }else {
            existedIds.add(nodeId);
        }
        List<CircuitDiagramNodeMeterRelationDO> relations = nodeToMeterId.get(nodeId);
        if (CollectionUtil.isEmpty(relations)) {
            List<CircuitDiagramNodeDO> children = parentIdToChildren.get(nodeId);
            for (CircuitDiagramNodeDO child : children) {
                getEnergyMeterIdsByCircuitDiagramNodeId(child.getId(), existedIds, parentIdToChildren, nodeToMeterId, meterIds);
            }
        }else {
            relations.forEach(r -> meterIds.add(r.getMeterId()));
        }
    }

    public List<Long> getAllMeterIdsByBuilding(Long buildingId) {
        List<Long> diagramNodeIds = circuitDiagramNodeEnergySubjectRelationManager.getDiagramNodeIds(ListUtil.of(buildingId), SubjectTypeEnum.BUILDING);
        if (CollectionUtil.isEmpty(diagramNodeIds)) {
            return diagramNodeIds;
        }
        List<Long> children = circuitDiagramNodeManager
                .getAllMap()
                .values()
                .stream()
                .filter(n -> StringUtils.hasLength(n.getPath()))
                .filter(n -> {
                    return diagramNodeIds.stream().anyMatch(id -> n.getPath().contains(String.format(EmsConst.PATH_SPLIT + id + EmsConst.PATH_SPLIT)));
                }).map(CircuitDiagramNodeDO::getId).collect(Collectors.toList());
        if (CollectionUtil.isNotEmpty(children)) {
            diagramNodeIds.addAll(children);
        }
        Map<Long, CircuitDiagramNodeMeterRelationDO> allMap = circuitDiagramNodeMeterRelationManager.getAllMap();
        return allMap
                .values()
                .stream()
                .filter(r -> diagramNodeIds.contains(r.getDiagramNodeId()))
                .map(CircuitDiagramNodeMeterRelationDO::getMeterId)
                .distinct()
                .collect(Collectors.toList());
    }

    public List<Long> getMeterIdsBySubjectId(Long subjectId, SubjectTypeEnum subjectType) {
        if (subjectId == null || subjectType == null) {
            return new ArrayList<>();
        }
        if (subjectType.equals(SubjectTypeEnum.INSTITUTE)) {
            List<Long> instituteIds = virtualInstituteDeal(ListUtil.of(subjectId));
            return instituteIds.stream().flatMap(id -> getEnergyMeterIdsByInstitutionId(id).stream()).collect(Collectors.toList()) ;
        }else if (subjectType.equals(SubjectTypeEnum.BUILDING)) {
            return getEnergyMeterIdsByBuildingId(subjectId);
        }else if (subjectType.equals(SubjectTypeEnum.DEPARTMENT)) {
            return getEnergyMeterIdsByDepartmentId(subjectId);
        }
        return new ArrayList<>();
    }
    public List<Long> getMeterIdsByDepartment(Long departmentId) {
        List<Long> diagramNodeIds = circuitDiagramNodeEnergySubjectRelationManager.getDiagramNodeIds(ListUtil.of(departmentId), SubjectTypeEnum.DEPARTMENT);
        if (CollectionUtil.isEmpty(diagramNodeIds)) {
            return diagramNodeIds;
        }
        List<Long> children = circuitDiagramNodeManager
                .getAllMap()
                .values()
                .stream()
                .filter(n -> StringUtils.hasLength(n.getPath()))
                .filter(n -> {
                    return diagramNodeIds.stream().anyMatch(id -> n.getPath().contains(String.format(EmsConst.PATH_SPLIT + id + EmsConst.PATH_SPLIT)));
                }).map(CircuitDiagramNodeDO::getId).collect(Collectors.toList());
        if (CollectionUtil.isNotEmpty(children)) {
            diagramNodeIds.addAll(children);
        }
        return circuitDiagramNodeMeterRelationManager.getAllMap().values().stream().filter(r -> diagramNodeIds.contains(r.getId())).map(CircuitDiagramNodeMeterRelationDO::getMeterId).distinct().collect(Collectors.toList());
    }

    public List<Long> getBuildingIdsByInstitution(Long institutionId) {
        return buildingInfoManager
                .getAllMap()
                .values()
                .stream()
                .filter(b -> institutionId.equals(b.getInstituteId()))
                .map(BuildingInfoDO::getId)
                .collect(Collectors.toList());
    }

    public List<Long> getAllMeterIdsByInstitution(Long institutionId) {
        return getBuildingIdsByInstitution(institutionId).stream()
                .flatMap(id -> getAllMeterIdsByBuilding(id).stream()).collect(Collectors.toList());
    }
}
